/**!
搜索部分的数据结构定义，以及gfsc关于search/grep的接口实现。
*/
use serde::{Deserialize, Serialize};

#[cfg(feature = "tracelog")]
use tracing::{debug, error, info};
#[cfg(feature = "rotatelog")]
use log::{debug, error, info, warn};

use crate::*;

/*
pub struct SearchResult {
    q: String,
    page:usize,    
    pub hits: Vec<SearchDoc>   
}
*/

/**
REST请求返回的索引结果的结构定义
 - docid是对文件内容的sha256，fname是gfs的保存路径全名：fhash/idx/filename
 - 可以借助get_element_by_search_fname(fname:&str)->(i32, i32, String)还原文件的3个定位元素。
 */
#[derive(Serialize, Deserialize, Debug, Default)]
pub struct SearchDoc {
    pub   docid: String,
    pub   fname: String,
    pub   content: String,
    pub   score: f32
}


/**
REST请求中， 用于传递搜索请求的参数结构
 */
#[allow(dead_code)]
#[derive(Debug, Deserialize)]
pub struct SearchParams {
    #[serde(default = "default_usize")]
    pub page: usize,    
    #[serde(default = "default_string")]
    pub q: String,
    #[serde(default = "default_string")]
    pub t: String,
}
/**
针对特定节点（Node）的搜索（没有pub，内部使用）, reader可以借助2个函数得到：
  - gfs_reader = router.get_reader(dynasty, ihash)?;     
  - let readers = router.get_dynasty_readers(dynasty)?;  
 */
async fn gfsc_node_search(gfs_reader: &str, para: &SearchParams ) -> Result< Vec < SearchDoc > > {
    let client = reqwest::Client::new();
    let mut headers = HeaderMap::new();
    headers.insert("Content-Type", "application/json; charset=utf-8".parse()?);
    headers.insert("Authorization", format!("Bearer {}", "token").parse()?);
    let qry_url = format!("{}/search?q={}&t={}&page={}", gfs_reader, urlencoding::encode(para.q.as_str()), para.t, para.page);
    debug!(?qry_url);
    let  r = client
        .get(qry_url)
        .headers(headers)        
        .send()
        .await?
        .json()
        .await?;
    return Ok(r);
}

/**
针对特定dynasty进行的全域异步搜索，系统会对每个独立Node发起搜索，然后对结构进行合并和重新按score排序
 - let readers = router.get_dynasty_readers(dynasty)?;  
 - 参数page在这里只是针对独立node的相对值，此函数返回的结果是每个node返回的其第page页的聚合结果。
 - 所以，搜索结果并不是全域（dynasty）范围的score排序结果，是相对结果集的重编，但第一页是绝对有效的。
 */
pub async fn gfsc_search( router: &GfsRouter, dynasty:usize, para: &SearchParams ) -> Result< Vec < SearchDoc >  > {
    let readers = router.get_dynasty_readers(dynasty)?;  
    debug!(?readers);

    let mut sresults: Vec<SearchDoc>  = Vec::new();
    // let keys = readers.
    // for i in 0..readers.len(){
    for (_key, reader) in readers.iter() {
        if let Ok(mut rets) = gfsc_node_search( reader, para).await {
            if rets.len() > 0{
                sresults.append(&mut rets);
            }
        }        
    }      

    sresults.sort_by(|a,b|{ b.score.partial_cmp(&a.score).unwrap() });
    return Ok(sresults);
}

/**
针对特定dynasty进行的全域异步grep，系统会对每个独立Node发起grep，然后对结构进行合并
  - let readers = router.get_dynasty_readers(dynasty)?;  
  - grep只是针对文件名，借助stat表数据进行的查找。
  - 实际，也可以通过gfsc_search,指定搜索模式，得到相同的功能。
 */
pub async fn gfsc_grep_file( router: &GfsRouter, dynasty:usize , pattern: &str ) -> Result< Vec<SearchDoc> > {
    let readers = router.get_dynasty_readers(dynasty)?;
    let mut gresults:Vec<SearchDoc> =  Vec::new();
    //for i in 0..readers.len(){
    for ((min, max), reader) in readers.iter() {    
        if let Ok(mut rets )= gfsc_node_grep( reader, pattern).await {            
            if rets.len() > 0 {
                gresults.append(&mut rets);             
            }
        }else{ 
            error!("error from range[{}-{}],  {}", min, max , reader);
        }
    }              
    return Ok(gresults);
}

/**
针对特定节点（Node）的文件名grep（没有pub，内部使用）, reader可以借助2个函数得到：
  - gfs_reader = router.get_reader(dynasty, ihash)?;     
  - let readers = router.get_dynasty_readers(dynasty)?;  
  - grep只是针对文件名，借助stat表数据进行的查找。
  - 实际，也可以通过gfsc_node_search,指定搜索模式，得到相同的功能。
 */
async fn gfsc_node_grep(gfs_reader: &str, pattern: &str ) -> Result< Vec<SearchDoc> > {
    let client = reqwest::Client::new();
    let mut headers = HeaderMap::new();
    headers.insert("Content-Type", "application/json; charset=utf-8".parse()?);
    headers.insert("Authorization", format!("Bearer {}", "token").parse()?);
    let qry_url = format!("{}/grep/file/{}", gfs_reader, urlencoding::encode(pattern));
    let  r = client
        .get(qry_url)
        .headers(headers)        
        .send()
        .await?
        .json()
        .await?;
    return Ok(r);
}

/**
获取一个文件名在一个指定dynasty的定位信息Stat
 * 不同于grep，这里的文件名是全匹配，而grep是文件名局部匹配
 * 因为文件名指定，所以node定位清楚，可以直接指定reader
 * 所以，可能存在多个相同名字的多条数据
 * whereis的基础，是基于stat表数据实现的，但name没有创建index，希望fhash的索引已经最大化过滤了数据，降低name匹配的效率损失
 */
pub async fn gfsc_whereis(router: &GfsRouter, dynasty:usize,  fname:&str) -> Result<Vec<Stat>> {

    let ihash = gfs_filename_hash(fname);        
    let reader = router.get_reader(dynasty, ihash)?;        

    let client = reqwest::Client::new();
    let mut headers = HeaderMap::new();
    headers.insert("Content-Type", "application/json; charset=utf-8".parse()?);
    headers.insert("Authorization", format!("Bearer {}", "token").parse()?);    
    let get_stat_url = format!("{}/whereis/{}",reader, urlencoding::encode(fname));
    let r: Vec<Stat> = client
        .get(get_stat_url)
        .headers(headers)
        .send()
        .await?
        .json()
        .await?;
    return Ok(r);
}


#[test]
fn search_doc_sort_test(){
    let mut rets :Vec<SearchDoc> = Vec::new();
    let a = SearchDoc{docid:"aaaaaaaaaa".to_string(), fname:"a".to_string(), content:"content".to_string(), score:1.2 };
    let b = SearchDoc{docid:"bbbbbbbbb".to_string(), fname:"b".to_string(), content:"content".to_string(), score:2.0 };
    let c = SearchDoc{docid:"ccccccccc".to_string(), fname:"c".to_string(), content:"content".to_string(), score:0.2 };
    rets.push(a);
    rets.push(b);
    rets.push(c);

    println!("{:#?}", rets);
    rets.sort_by(|a,b|{ b.score.partial_cmp(&a.score).unwrap()});
    println!("{:#?}", rets);
}

#[test]
fn search_node_test(){

    async fn search_node_in_test()->Result<  Vec < SearchDoc >  > {
        let reader = "http://192.168.2.104:7002";
        let para = &SearchParams { page: 1, q: "消费".to_string(), t:"".to_string() };
        let ret = gfsc_node_search(reader, para).await?;
        println!("{:#?}", ret);
        Ok(ret)
    }

    let f = search_node_in_test();
    let rt = tokio::runtime::Runtime::new().unwrap();
    let r = rt.block_on(f).unwrap();    
    println!("{:#?}", r);
}


#[test]
fn search_node_test2(){
    async fn search_node_in_test2()->Result<()> {
        let reader = "http://192.168.2.104:7002";
        let para = &SearchParams { page: 1, q: "消费".to_string(), t:"".to_string() };


        let client = reqwest::Client::new();
        let mut headers = HeaderMap::new();
        headers.insert("Content-Type", "application/json; charset=utf-8".parse()?);
        headers.insert("Authorization", format!("Bearer {}", "token").parse()?);
        let qry_url = format!("{}/search?q={}&t={}&page={}", reader, urlencoding::encode(para.q.as_str()), para.t, para.page);
        println!("{:#?}", qry_url);
        let  r = client
            .get(qry_url)
            .headers(headers)            
            .send()
            .await?
            .text().await?;
            //.json()
            //.await?;
        debug!(?r);        
        Ok(())       
    }

    let f = search_node_in_test2();
    let rt = tokio::runtime::Runtime::new().unwrap();
    let r = rt.block_on(f).unwrap();    
    println!("{:#?}", r);
}